#include "client.hpp"

#include <QIODevice>
#include <QDebug>
#include <QHostAddress>

#include "mainwindow.hpp"

Client::Client(MainWindow * mainWindow) :
        QObject(mainWindow),
        _mainWindow(mainWindow),
        _socket(new QTcpSocket(this)),
        _paquet(new QByteArray()),
        _in(new QDataStream(_socket)),
        _out(new QDataStream(_paquet, QIODevice::WriteOnly)),
        _messageSize(0),
        _connected(false)
{
    connect(_socket, SIGNAL(connected()), this, SIGNAL(connectionReussie()));
    connect(_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SIGNAL(connectionRatee()));
    connect(_socket, SIGNAL(disconnected()), this, SLOT(deconnexion()));

    connect(_socket, SIGNAL(readyRead()), this, SLOT(donneesRecues()));

    connect(this, SIGNAL(NTconnectionFailed(quint8)), this, SLOT(reactConnectionFailed(quint8)));
    connect(this, SIGNAL(NTconnected()), this, SLOT(reactConnected()));
    connect(this, SIGNAL(NTconnected()), mainWindow, SLOT(reactConnected()));
    connect(this, SIGNAL(NTinscriptionFailed()), this, SLOT(reactInscriptionFailed()));
    connect(this, SIGNAL(NTinscriptionSuccessful()), this, SLOT(reactInscriptionSuccessful()));

    connect(this, SIGNAL(NTillegalMessage(QString)), this, SLOT(reactIllegalMessage(QString)));
    connect(this, SIGNAL(NTnotExists(quint8)), this, SLOT(reactNotExists(quint8)));

}

Client::~Client()
{
    delete _paquet;
    delete _in;
    delete _out;
}

void Client::deconnexion()
{
    QMessageBox::critical(_mainWindow, "Server internal error", QString("Connection interrupted"));
    _connected = false;
    emit disconnected();
}

void Client::prePaquet()
{
    delete _out;
    delete _paquet;

    _paquet = new QByteArray();
    _out = new QDataStream(_paquet, QIODevice::WriteOnly);

    (*_out) << (quint16) 0; // On écrit 0 au début du paquet pour réserver la place pour écrire la taille
}

void Client::postPaquet()
{
    _out->device()->seek(0); // On se replace au début du paquet
    (*_out) << (quint16) (_paquet->size() - sizeof(quint16)); // On écrase le 0 qu'on avait réservé par la longueur du message
}

void Client::writeStamp(Mid::MessageID mid)
{
    (*_out) << (quint8) mid;
}

void Client::connecter(const QString & ip, quint16 port)
{
    if (!_connected)
    {
        if (_socket->state() != QAbstractSocket::ConnectingState)
        {
            disconnect(_socket, SIGNAL(disconnected()), this, SLOT(deconnexion()));
            _socket->abort();
            connect(_socket, SIGNAL(disconnected()), this, SLOT(deconnexion()));

            _socket->connectToHost(ip, port);
        }
        else
            emit connectionReussie();
    }
}

void Client::reactInscriptionSuccessful()
{
    emit connectionReussie();
}

void Client::reactInscriptionFailed()
{
    QMessageBox::critical(_mainWindow, "Error", QString("This login is already registered"));
}

void Client::reactConnected()
{
    _connected = true;
}

void Client::reactConnectionFailed(quint8 failReason)
{
    if(failReason == 0)
    {
        if(QMessageBox::critical(_mainWindow, "Error", "This login doesn't exist.\nDo you want to create it?", QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)
        {
            emit inscription();
        }
    }
    else if (failReason == 1)
    {
        QMessageBox::critical(_mainWindow, "Error", "Wrong password");
    }
    else if (failReason == 2)
    {
        QMessageBox::critical(_mainWindow, "Error", "Invalid strings");
    }
    else if (failReason == 3)
    {
        QMessageBox::critical(_mainWindow, "Error", "Login already being used!");
    }
    else
        qWarning() << "protocol error...";

    emit connectionRatee();
}

void Client::donneesRecues()
{
    delete _in;
    _in = new QDataStream(_socket);

    if (_messageSize == 0) // Si taille du message inconnue
    {
        if (_socket->bytesAvailable() < (int)sizeof(quint16)) // Si on ne peut pas encore récupérer la taille
             return;

        (*_in) >> _messageSize; // On récupère la taille du message
    }

    if (_socket->bytesAvailable() < _messageSize) // Si on n'a pas reçu tout le message, on quitte la fonction
        return;

    quint8 stamp;
    (*_in) >> stamp;


    dispatch((Mid::MessageID)stamp);

    _messageSize = 0; // Pour les prochains messages

    if (_socket->bytesAvailable() > 0) // Si jamais on reçoit plusieurs messages d'un coup, il faut pouvoir les lire également
        donneesRecues();
}

void Client::dispatch(Mid::MessageID mid)
{
    if (mid == Mid::CONNECTED or
        mid == Mid::INSCRIPTIONFAILED or
        mid == Mid::INSCRIPTIONSUCCESSFUL) // void
    {
        if (mid == Mid::CONNECTED)
            emit NTconnected();
        else if (mid == Mid::INSCRIPTIONSUCCESSFUL)
            emit NTinscriptionSuccessful();
        else
            emit NTinscriptionFailed();
    }
    else if (mid == Mid::MOTD or
             mid == Mid::LOBBYSAID or
             mid == Mid::CLIENTCONNECTED or
             mid == Mid::CLIENTDISCONNECTED or
             mid == Mid::ILLEGALMESSAGE) // QString
    {
        QString s;
        (*_in) >> s;

        if (mid == Mid::MOTD)
            emit NTmotd(s);
        else if (mid == Mid::LOBBYSAID)
            emit NTlobbySaid(s);
        else if (mid == Mid::CLIENTCONNECTED)
            emit NTclientConnected(s);
        else if (mid == Mid::CLIENTDISCONNECTED)
            emit NTclientDisconnected(s);
        else
            emit NTillegalMessage(s);
    }
    else if (mid == Mid::CONNECTIONFAILED or
             mid == Mid::GAMEDESTROYED or
             mid == Mid::NOTEXISTS or
             mid == Mid::BADMOVE or
             mid == Mid::MOVESFINISHED or
             mid == Mid::TOPLAYAGAIN) // quint8
    {
        quint8 i;
        (*_in) >> i;

        if (mid == Mid::CONNECTIONFAILED)
            emit NTconnectionFailed(i);
        else if (mid == Mid::GAMEDESTROYED)
            emit NTgameDestroyed(i);
        else if (mid == Mid::NOTEXISTS)
            emit NTnotExists(i);
        else if (mid == Mid::BADMOVE)
            emit NTbadMove(i);
        else if (mid == Mid::MOVESFINISHED)
            emit NTmovesFinished(i);
        else
            emit NTtoPlayAgain(i);
    }
    else if (mid == Mid::GAMELEFT or
             mid == Mid::TURNSKIPPED or
             mid == Mid::GROUPSVALIDATED) // quint8, quint8
    {
        quint8 i0, i1;

        (*_in) >> i0 >> i1;

        if (mid == Mid::GAMELEFT)
            emit NTgameLeft(i0, i1);
        else if (mid == Mid::TURNSKIPPED)
            emit NTturnSkipped(i0, i1);
        else
            emit NTgroupsValidated(i0, i1);
    }
    else if (mid == Mid::NEWSCORE) // quint8, quint8, qreal
    {
        quint8 i0, i1;
        qreal r0;

        (*_in) >> i0
               >> i1
               >> r0;

        emit NTnewScore(i0, i1, r0);
    }
    else if (mid == Mid::HESEES or
             mid == Mid::HEDOESNTSEEANYMORE or
             mid == Mid::GAMESTARTS or
             mid == Mid::GAMESAID) // quint8, QString
    {
        quint8 i;
        QString s;

        (*_in) >> i >> s;

        if (mid == Mid::HESEES)
            emit NTheSees(i, s);
        else if (mid == Mid::HEDOESNTSEEANYMORE)
            emit NTheDoesntSeeAnymore(i, s);
        else if (mid == Mid::GAMESTARTS)
            emit NTgameStarts(i, s);
        else
        {
            emit NTgameSaid(i, s);
        }
    }
    else if (mid == Mid::CLIENTLIST) // QStringList
    {
        QStringList s;

        (*_in) >> s;

        emit NTclientList(s);
    }
    else if (mid == Mid::GAMECREATED) // quint8, quint8, qreal, QString
    {
        quint8 i0, i1;
        qreal r0;
        QString s0;

        (*_in) >> i0 >> i1 >> r0 >> s0;

        emit NTgameCreated(i0, i1, r0, s0);
    }
    else if (mid == Mid::GAMELIST) // complex
    {
        quint16 size;
        quint8 id, goban;
        qreal komi;
        QString black, white;
        QList<Game *> list;
        Game * game;

        (*_in) >> size;

        for (int i = 0; i < size; ++i)
        {
            (*_in)  >> id
                    >> goban
                    >> komi
                    >> black
                    >> white;

            game = new Game(_mainWindow->lobby(), this, id, goban, komi, black, white, _mainWindow);

            list.append(game);
        }

        emit NTgameList(list);
    }
    else if (mid == Mid::GAMESTATE) // quint8, QStringList, QVector < QVector<quint8> >
    {
        quint8 i;
        QStringList qsl;
        QVector< QVector<quint8> > v;

        (*_in) >> i
               >> qsl
               >> v;

        emit NTgameState(i, qsl, v);
    }
    else if (mid == Mid::OBSERVERLIST) // quint8, QStringList
    {
        quint8 i;
        QStringList qsl;

        (*_in) >> i
               >> qsl;

        emit NTobserverList(i, qsl);
    }
    else if (mid == Mid::MOVEPLAYED) // quint8, quint8, quint8, quint8
    {
        quint8 i0, i1, i2, i3;

        (*_in) >> i0
               >> i1
               >> i2
               >> i3;

        emit NTmovePlayed(i0, i1, i2, i3);
    }
    else if (mid == Mid::GROUPKILLED or // quint8, quint8, quint8
             mid == Mid::GROUPSAVED)
    {
        quint8 i0, i1, i2;

        (*_in) >> i0
               >> i1
               >> i2;

        if (mid == Mid::GROUPKILLED)
            emit NTgroupKilled(i0, i1, i2);
        else
            emit NTgroupSaved(i0, i1, i2);
    }
    else if (mid == Mid::GAMEOVER) // quint8, qreal, qreal
    {
        quint8 i;
        qreal r0, r1;

        (*_in) >> i
               >> r0
               >> r1;

        emit NTgameover(i, r0, r1);
    }
    else
        qWarning() << "Unknown incoming message from the server, mid = " << mid;
}

const QTcpSocket * Client::socket()
{
    return _socket;
}

void Client::send(Mid::MessageID mid, const QString & s0)
{
    prePaquet();

    writeStamp(mid);
    (*_out) << s0;

    postPaquet();

    _socket->write(*_paquet);
}

void Client::send(Mid::MessageID mid, quint8 i0)
{
    prePaquet();

    writeStamp(mid);
    (*_out) << i0;

    postPaquet();

    _socket->write(*_paquet);
}

void Client::send(Mid::MessageID mid, quint8 i0, const QString & s0)
{
    prePaquet();

    writeStamp(mid);
    (*_out) << i0 << s0;

    postPaquet();

    _socket->write(*_paquet);
}

void Client::send(Mid::MessageID mid, quint8 i0, qreal r0)
{
    prePaquet();

    writeStamp(mid);
    (*_out) << i0 << r0;

    postPaquet();

    _socket->write(*_paquet);
}

void Client::send(Mid::MessageID mid, const QString & s0, const QString & s1)
{
    prePaquet();

    writeStamp(mid);
    (*_out) << s0 << s1;

    postPaquet();

    _socket->write(*_paquet);
}

void Client::send(Mid::MessageID mid)
{
    prePaquet();

    writeStamp(mid);

    postPaquet();

    _socket->write(*_paquet);
}

void Client::send(Mid::MessageID mid, quint8 i0, quint8 x, quint8 y)
{
    prePaquet();

    writeStamp(mid);
    (*_out) << i0 << x << y;

    postPaquet();

    _socket->write(*_paquet);
}

void Client::reactIllegalMessage(const QString & message)
{
    QMessageBox::warning(_mainWindow, "Warning", QString("Illegal message : " + message));
}

void Client::reactNotExists(quint8 idPartie)
{
    QMessageBox::warning(_mainWindow, "Warning", QString("The game " + QString::number(idPartie) + " doesn't exist"));
}
